/**
 * 
 * Copyright (c) 2009 Tridium, Inc
 * Licensed under the MIT License
 *
 * oBIX Watch Helper
 * -----------------
 * 
 * A JavaScript Library to manage an oBIX Watch. Typically, a page will have one oBIX Watch that
 * has URIs added and removed from it.
 * 
 * The lifecycle consists of the following...
 * 
 * var obixWatch = new ObixWatch(); 
 *
 * // Start the Watch
 * obixWatch.startWatch();
 * 
 * // Add a number of URIs with registered JavaScript function call backs
 * obixWatch.addURIs(uris);
 * 
 * ...
 * 
 * // Remove the URIs (if wanted)
 * obixWatch.removeURIs(uris);
 * 
 * ...
 * 
 * // When the page unloads stop the Watch
 * obixWatch.stopWatch();
 * 
 * When calling the 'addURI' function, an array of URI objects needs to be passed in with the following Properties...
 * 
 * uri: A valid oBIX URI -> '/obix/config/Drivers/NiagaraNetwork/NiagaraStation/points/SineWave/out/value/'
 * handler: A function call back for when an update is received for the URI. For example, see obixWatch.defaultHtmlUpdate for the function specification. 
 * dispName: A display name reference for the URI
 * inBody: If adding an oBIX feed, this is the oBIX that's passed in as the in argument to the feed
 * pickle: Is passed back when the handler is invoked (on an update)
 *
 * @author		gjohnson
 * @creation 	27 Jan 2009
 * @version 	1
 */ 
function ObixWatch()
{
  ////////////////////////////////////////////////////////////////
  // Properties
  ////////////////////////////////////////////////////////////////
 
  var self = this;
 
  var uriList = new Array();
  var myWatch = null;
  var myLease = null;
  var myRelLease = null;
  var watchChanges = null; 

  var pollChangesTime = 5000;

  var intervalId = -1;  

  ////////////////////////////////////////////////////////////////
  // Methods to start an oBIX watch
  ////////////////////////////////////////////////////////////////
  
  /**
   * Start the Watch (optional poll changes time argument - caps at 30 seconds)
   */
  this.startWatch = function(newPollChangesTime)
  { 
    if (typeof newPollChangesTime != 'undefined')
    {
      pollChangesTime = newPollChangesTime;
      
      if (pollChangesTime > 30000)
        pollChangesTime = 30000;
    }
    
    var msg = new AjaxMessage();
    msg.setHeader("Content-Type", "text/xml");
    msg.send("POST", "/obix/watchService/make/", "", initLease);  
  }
  
  /**
   * Set up the lease time
   */
  function initLease(resp)
  {              
    myLease = resp.responseXML.getElementsByTagName("reltime").item(0).getAttribute("href");
    myRelLease = myLease.substring(myLease.indexOf("/", 7));
    myWatch = myRelLease.substring(0,myRelLease.length-6); 
    watchChanges = myWatch + "pollChanges/";
    
    //set Lease Time
    var msg = new AjaxMessage();
    msg.setHeader("Content-Type", "text/xml");
    var data0 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><reltime href=\"" + myLease + "\" val=\"PT60S\" />";
    msg.send("PUT", myRelLease, data0, startWatchPoll);  
  } 
  
  /**
   * Start polling the Watch
   */   
  function startWatchPoll(resp) 
  {
    pollChanges();
    intervalId = setInterval(pollChanges, pollChangesTime);
    
    self.addURIs([]);
  }
  
  ////////////////////////////////////////////////////////////////
  // Methods to maintain an oBIX Watch
  ////////////////////////////////////////////////////////////////

  /**
   * Poll for any changes to the Watch (called regulary by Interval Timer)
   */
  function pollChanges() 
  {
    var msg = new AjaxMessage();
    msg.setHeader("Content-Type", "text/xml");
    msg.send("POST", watchChanges, "", handleList);  
  }
   
  /**
   * Callback for handling any poll changes response oBIX list
   */   
  function handleList(resp)
  {    
    if (uriList.length == 0) return;
    
    try
    {
      // If an oBIX error is found then attempt to recreate the Watch
      for (var x = 0; x < resp.responseXML.childNodes.length; ++x)
      {
        if (resp.responseXML.childNodes[x].nodeName == "err" && resp.responseXML.childNodes[x].getAttribute("is") == "obix:BadUriErr")
        {
          // Stop the Watch refresh timer
          if (intervalId > -1)
          {
            clearInterval(intervalId);
            intervalId = -1;
          }
          
          // Restart the Watch
          self.startWatch();
          return;
        }
      }
    
      // Find the elements in the oBIX list and try to find a match for an update
      var items = resp.responseXML.getElementsByTagName("list");
      if (items == null) return;
      
      var nodes = items[0].childNodes;
      if (nodes == null) return;
          
      for (var i=0; i<=nodes.length; i++)
      {
        if (nodes[i] != null)
        {
          // Skip if not a DOM Element
          if (nodes[i].nodeType != 1) continue;
          
          var href = nodes[i].getAttribute("href");

          if (href != null)
          {           
            for (var j=0;j<uriList.length;j++)  
            {
              if (uriList[j].uri == href)
                uriList[j].handler(uriList[j], nodes[i]);
            }
          }
        }
      }
    }
    catch(err)
    {
      alert("Error: " + err + " line: " + err.lineNumber);
    }
  }
  
  ////////////////////////////////////////////////////////////////
  // Standard HTML Update Handler
  ////////////////////////////////////////////////////////////////
  
  this.defaultHtmlUpdate = function(uriItem, node)
  {    
    var element = document.getElementById(uriItem.pickle);
    
    if (element != null)
    {
      var v = node.getAttribute("display");
      
      if (v == null)
        v = node.getAttribute("val");
      
      element.innerHTML = v;
    }
  }
  
  ////////////////////////////////////////////////////////////////
  // Methods to add and remove URIs to the Watch
  ////////////////////////////////////////////////////////////////
  
  this.removeURIs = function(newUriList)
  {
    if (uriList.length == 0) return;
    
    // Filter the current array
    var a = new Array();
    for (var i = 0; i < uriList.length; ++i)
    {
      var found = false;
    
      for (var j = 0; j < newUriList.length; ++j)
      {
        if (uriList[i].uri == newUriList[j].uri &&
            uriList[i].handler == newUriList[j].handler &&
            uriList[i].dispName == newUriList[j].dispName &&
            uriList[i].pickle == newUriList[j].pickle)
        {
          found = true;
          break;
        }
      }
      
      if (!found)
        a.push(uriList[i]);
    }
    
    uriList = a;
        
    var msg = new AjaxMessage();
    msg.setHeader("Content-Type", "text/xml");
    msg.send("POST", myWatch + "remove/", createWatchInXml(newUriList), removeSuccess);
  }
  
  function removeSuccess(resp)
  {    
    //alert("point removed: \n" + resp.responseText);
  }
  
  this.addURIs = function(newUriList)
  {    
    // Add to the current list of items we are watching
    uriList = uriList.concat(newUriList);
    
    // oBIX Watch hasn't started yet so wait until we have all the information
    if (watchChanges != null)
    {        
      var msg = new AjaxMessage();
      msg.setHeader("Content-Type", "text/xml");
      msg.send("POST", myWatch + "add/", createWatchInXml(uriList), addSuccess);
    }
  }

  function addSuccess(resp)
  {
    //alert("point added: \n" + resp.responseText);
    handleList(resp);
  }
    
  ////////////////////////////////////////////////////////////////
  // Methods to delete a Watch
  ////////////////////////////////////////////////////////////////
  
  /**
   * Delete the Watch
   */
  this.stopWatch = function()
  {
    if (intervalId > -1)
    {
      clearInterval(intervalId);
      intervalId = -1;
    }
    
    var msg = new AjaxMessage();
    msg.setHeader("Content-Type", "text/xml");
    msg.send("POST", myWatch + "delete/", "", deleteWatchHandler);
  }
  
  /**
   * Handler for the delete Watch response
   */
  function deleteWatchHandler(resp)
  {
    //alert("Watch deleted: \n" + resp.responseText);
  }
  
  ////////////////////////////////////////////////////////////////
  // Utility Methods
  ////////////////////////////////////////////////////////////////
  
  /**
   * Create WatchIn oBIX Xml
   */  
  function createWatchInXml(uriList)
  {
    var xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><obj is=\"obix:WatchIn\"><list name=\"hrefs\" of=\"obix:Uri\">";
    
    for (var i = 0; i < uriList.length; ++i)
      xml += "<uri val=\"" + uriList[i].uri + "\">" + uriList[i].inBody + "</uri>";
    
    xml += "</list></obj>";
    return xml;
  }
} 

////////////////////////////////////////////////////////////////
// XmlHttp
////////////////////////////////////////////////////////////////

function AjaxMessage()
{
  /**
   * Return whether IE is being used or not
   */
  AjaxMessage.isIe = function()
  {
    if (typeof this.ie == 'undefined')
    {    
      var browser = navigator.appName.toLowerCase();
      this.ie = browser == "microsoft internet explorer";
    }
    
    return this.ie;
  }
  
  var headers = new Array();
  var responseHandler = null;
  var xmlhttp = (AjaxMessage.isIe())
    ? new ActiveXObject("Msxml2.XMLHTTP")
    : new XMLHttpRequest();
  var err_LostConnection = "Session disconnected";
  
  /**
   * Set the specified HTTP header.
   */
  this.setHeader = function(name, value)
  {
    headers.push({name:name, value:value });
  }

  /**
   * Send a message.
   */
  this.send = function(method, url, body, handler)
  {
    try
    {
      responseHandler = handler;
      xmlhttp.open(method, url, true);
      for (var i=0; i<headers.length; i++)
      {
        var header = headers[i];
        xmlhttp.setRequestHeader(header.name, header.value);
      }

      // 24 Aug 07 - AndyF: Added for IE cache issues (when using a proxy server).
      if (AjaxMessage.isIe())
        xmlhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");

      xmlhttp.onreadystatechange = handleResponse;
            
      xmlhttp.send(body);
    }
    catch (err)
    {
      doError("AjaxMessage error", null, err);
    }
  }

  /**
   * Handle request response.
   */
  function handleResponse()
  {
    try
    {
      if (xmlhttp.readyState == 4)
      {
        var status = parseInt(xmlhttp.status);
        
        // Filter out IE bugs (IE is fussy and throws errors if the ServerSocket doesn't read all of the data in)
        if (status < 12030)
        {
          if (status != 200)
          {
            var text = xmlhttp.responseText;
            if (text.length == 0) text = err_LostConnection;
            doError(text, null, xmlhttp.status);
          }
          else if (responseHandler != null)
            responseHandler(xmlhttp);
        }
 
        // 23 Oct 07 - AndyF: make sure we remove circular
        // dependancies to prevent memory leaks in IE
        xmlhttp.onreadystatechange = new function() {};
        xmlhttp = null;
      }
    }  
    catch (err)
    {
      // 23 Oct 07 - AndyF: make sure we remove circular
      // dependancies to prevent memory leaks in IE
      xmlhttp.onreadystatechange = new function() {};
      xmlhttp = null;

      doError(err_LostConnection, null, err);
    }
  }
  
  /**
   * Handle any errors in the response
   */
  function doError(message, val, err)
  {
    alert("Error -> msg: " + message + " val: " + val + " err: " + err);
  }
}